#include "freeformrulebase.h"
#include <algorithm>
#include <assert.h>
#include "rulebase.h"
#include "freefuncs.h"

extern ofstream logFile;

namespace utilityFunc
{
	/** Tells if the neighborhood whose index is given by nbIndex is important in the
	 * Rulebase rb or not. by important we mean we cannot remove that specific rulebase
	 * or the rulebase will not be the same.
	 */
	bool isImportant(const RuleBase2B& rb, int nbIndex)	
	{
		//if (rb.recalculateConflictNum() != rb.getConflictNum())
		//{
		//	cout << "Error: the given RuleBase2B has not an updated conflict num." << endl;
		//	assert (false);
		//}

		if (rb.getConflictNum() != 0)
		{
			std::cout << "Error: isImportant was called for a conflicting RuleBase2B object." << endl;
			assert (true);
		}
		FreeFormRuleBase freeRB(rb, false);
		//for (int i = 0; i < rb.getInstanceSize(); ++i)
		//{
		freeRB.removeFeature(nbIndex);
		if (freeRB.getConflictNum() > 0)
			return true;
		//}
		return false;
	}

		/** Tells if the neighborhood whose index is given by nbIndex is important in the
	 * Rulebase rb or not. by important we mean we cannot remove that specific rulebase
	 * or the rulebase will not be the same.
	 */
	bool isImportant(const FreeFormRuleBase& ffrb, int nbIndex)	
	{
		//if (rb.recalculateConflictNum() != rb.getConflictNum())
		//{
		//	cout << "Error: the given RuleBase2B has not an updated conflict num." << endl;
		//	assert (false);
		//}

		if (ffrb.getConflictNum() != 0)
		{
			std::cout << "Error: isImportant was called for a conflicting RuleBase2B object." << endl;
			assert (false);
		}
		FreeFormRuleBase freeRB(ffrb);
		freeRB.removeFeature(nbIndex);
		if (freeRB.getConflictNum() > 0)
			return true;
		//}
		return false;
	}

	void fillInAgeSortedArray(int* ageSortedArray, int arraySize, discardingOrder order)
	{
		assert (arraySize > 0 && arraySize % 2 == 1);
		assert (order == removeoldestfirst || order == removenewestfirst);
		int diameter = (int)(sqrt((double)(arraySize)));
		assert (diameter * diameter == arraySize);	//to make sure the array size is a complete square.
		assert (diameter % 2 == 1);	//diameter is always an even number;
		int radius = (diameter - 1) / 2;
		int center = radius;
		int ageArrayIndex = 0;
		ageSortedArray[ageArrayIndex++] = arraySize / 2;
		for (int r = 1; r <= radius; ++r)
		{
			int corner1RC = center - r;
			int corner2RC = center + r;

			//add the upper row
			for (int col = corner1RC; col < corner2RC; ++col)
			{
				ageSortedArray[ageArrayIndex++] = corner1RC*diameter + col; //MATRIX(corner1RC, col);
			}
			//add the right col
			for (int row = corner1RC; row < corner2RC; ++row)
			{
				ageSortedArray[ageArrayIndex++] = row*diameter + corner2RC; //MATRIX(row, corner2RC);
			}
			//add the lower row
			for (int col = corner2RC; col > corner1RC; --col)
			{
				ageSortedArray[ageArrayIndex++] = corner2RC*diameter + col; //MATRIX(corner2RC, col);
			}
			//add the left col
			for (int row = corner2RC; row > corner1RC; --row)
			{
				ageSortedArray[ageArrayIndex++] = row*diameter + corner1RC; //MATRIX(row, corner1RC);
			}
		}

		if (order == removenewestfirst) //we have to reverse the array in this case
			for (int i = 0; i < arraySize / 2; ++i)
			{
				int temp;
				temp = ageSortedArray[i];
				ageSortedArray[i] = ageSortedArray[arraySize - i - 1];
				ageSortedArray[arraySize - i - 1] = temp;
			}

	}


	void fillRemoveOrderArray(const RuleBase2B& rb2b, vector<int>& removeOrderArray, discardingOrder order)
	{
		int featureNum = rb2b.getTheSizeOfOneNeighborhood();
		RuleBase rb;
		int* sortedGainArray = NULL;
		int* ageSortedArray = NULL;
		int* randomPermutationArray = NULL;

		switch (order)
		{
		case (removelinear):
			for (int i = 0; i < featureNum; ++i)
				removeOrderArray.push_back(i);
			break;

		case (removelinearreverse):
			for (int i = featureNum - 1; i >= 0; --i)
				removeOrderArray.push_back(i);
			break;

		case (removeleastgainfirst_static):
		case (removeleastgainfirst_dynamic):
			ConvertRB2BtoRB(rb2b, rb);
			sortedGainArray = new int[featureNum];
			rb.getMostGainIndicesArray_SuperposeNBs(sortedGainArray, featureNum);
			for (int i = featureNum-1; i >= 0; --i)
				removeOrderArray.push_back(sortedGainArray[i]);
			delete[] sortedGainArray;
			break;

		case (removemostgainfirst_static):
		case (removemostgainfirst_dynamic):
			ConvertRB2BtoRB(rb2b, rb);
			sortedGainArray = new int[featureNum];
			rb.getMostGainIndicesArray_SuperposeNBs(sortedGainArray, featureNum);
			for (int i = 0; i < featureNum; ++i)
				removeOrderArray.push_back(sortedGainArray[i]);
			delete[] sortedGainArray;
			break;

			case (removeoldestfirst):
			ageSortedArray = new int[featureNum];
			fillInAgeSortedArray(ageSortedArray, featureNum, removeoldestfirst);
			for (int i = 0; i < featureNum; ++i)
				removeOrderArray.push_back(ageSortedArray[i]);
			delete[] ageSortedArray;
			break;

		case (removenewestfirst):
			ageSortedArray = new int[featureNum];
			fillInAgeSortedArray(ageSortedArray, featureNum, removenewestfirst);
			for (int i = 0; i < featureNum; ++i)
				removeOrderArray.push_back(ageSortedArray[i]);
			delete[] ageSortedArray;
			break;

		case (removerandom):
			randomPermutationArray = new int[featureNum];
			fillInRandomPermutationArray(randomPermutationArray, featureNum);
			for (int i = 0; i < featureNum; ++i)
				removeOrderArray.push_back(randomPermutationArray[i]);
			delete[] randomPermutationArray;
			break;
			
		default:
			assert(false);
		}
	}

	/**
	 * This function only removes an element from the array and does not re-calculate the gains of the elements
	 * to form a new order array after removing.
	 */
	void updateRemoveOrderArrayStatic(vector<int>& orderArray, int removeIndex) 
	{
		int pilotValue = orderArray[removeIndex];
		for (int i = 0; i < (int) removeIndex; ++i)
			if (orderArray[i] > pilotValue)
				--orderArray[i];
		for (int i = removeIndex; i < (int) orderArray.size()-1 ; ++i)
			if (orderArray[i+1] > pilotValue)
				orderArray[i] = orderArray[i+1] - 1;
			else
				orderArray[i] = orderArray[i+1];
		orderArray.pop_back();
	}

	//This function clears the whole removeOrderArray and rebuilds is from scratch according to the rulebase in ffrb.
	void updateRemoveOrderArrayDynamic(const FreeFormRuleBase& ffrb, vector<int>& removeOrderArray, discardingOrder order)
	{
		if (removeOrderArray.size() < 2)
			return;
		int featureNum = ffrb.getcompressedNBSize();
		int* sortedGainArray = NULL;
		removeOrderArray.clear();
		switch (order)
		{
		//case (removelinear):
		//	for (int i = 0; i < featureNum; ++i)
		//		removeOrderArray.push_back(i);
		//	break;

		//case (removelinearreverse):
		//	for (int i = featureNum - 1; i >= 0; --i)
		//		removeOrderArray.push_back(i);
		//	break;

		case (removeleastgainfirst_dynamic):
			sortedGainArray = new int[featureNum];
			ffrb.getMostGainIndicesArray_SuperposeNBs(sortedGainArray, featureNum);
			for (int i = featureNum-1; i >= 0; --i)
				removeOrderArray.push_back(sortedGainArray[i]);
			break;

		case (removemostgainfirst_dynamic):
			sortedGainArray = new int[featureNum];
			ffrb.getMostGainIndicesArray_SuperposeNBs(sortedGainArray, featureNum);
			for (int i = 0; i < featureNum; ++i)
				removeOrderArray.push_back(sortedGainArray[i]);
			break;
			
		default:
			cerr << "Error: static remove order array was asked to get updated dynamically." << endl;
			assert(false);
		}
		if (sortedGainArray != NULL)
			delete[] sortedGainArray;
	}


}

FreeFormRuleBase::FreeFormRuleBase()
{

}

FreeFormRuleBase::FreeFormRuleBase (const RuleBase2B& rb, bool doCompress, discardingOrder order): originalNBSize(rb.getInstanceSize())
{
	//assert (rb.getConflictNum() == 0 && "The given RuleBase has conflicting rules.");

	this->neighborIndices = NULL;
	if (doCompress)
		makeObjectFromRB2BandCompress(rb, order);
	else
		makeObjectFromRB2BandDontCompress(rb);
}

//void FreeFormRuleBase::makeObjectFromRB2BandCompress(const RuleBase2B& rb) //this implementation deletes the unimportant rules in oneshot, it's wrong.
//{
//	if (this->neighborIndices != NULL || cmpRule.size() != 0)
//	{
//		cerr << "Warning: this FreeFormRuleBase object has a not-null pointer, it's fishy!";
//		delete[] neighborIndices;
//		this->cmpRule.clear();
//		//assert(neighborIndices);
//	}
//	
//	compressedNBSize = 0; //initializing the value, will set later
//	int unCompressedSize = rb.getInstanceSize(); //un-compressed size of the rules.
//	bool* shouldKeep = new bool[unCompressedSize];
//	int featurePerNeighborhood = rb.getTheSizeOfOneNeighborhood();
//	for (int i = 0; i < featurePerNeighborhood; ++i)
//	{
//		if (utilityFunc::isImportant(rb,i))
//		{
//			shouldKeep[i] = true;
//			compressedNBSize++;
//		}
//		else
//			shouldKeep[i] = false;
//	}
//	neighborIndices = new int[compressedNBSize];
//	int compIndex = 0;	//counter for the compressed neighbors.
//	for (int i = 0; i < unCompressedSize; ++i)
//		if (shouldKeep[i])
//			neighborIndices[compIndex++] = i;
//	for (int i = 0; i < rb.getInstanceNum(); ++i)
//		cmpRule.push_back(FreeFormRule(rb.getInstance(i), neighborIndices, compressedNBSize));
//	clearRepeatingRules();
//}

void FreeFormRuleBase::makeObjectFromRB2BandCompress(const RuleBase2B& rb, discardingOrder order)
{
	int removedSoFar = 0;
	//ofstream debugFile ("debug.txt");
	//DEBUG(debugFile, "RuleBase2B object:");
	//DEBUG(debugFile, rb);
	if (this->neighborIndices != NULL || cmpRule.size() != 0)
	{
		cerr << "Warning: this FreeFormRuleBase object has a not-null pointer, it's fishy!";
		delete[] neighborIndices;
		this->cmpRule.clear();
		//assert(neighborIndices);
	}
	if (rb.getInstanceNum() == 1)
	{
		neighborIndices = new int[1];
		neighborIndices[0] = 0;
		cmpRule.push_back(FreeFormRule (rb.getInstance(0), neighborIndices, 1)); //there is only one LI in rb, any feature can be considered as the only important feature. We assume it is the first feature.
		neighborIndices = new int[1];
		neighborIndices[0] = 0;
		compressedNBSize = 1;
		nbPerRule = rb.getNBNumInOneInstance();
		originalNBSize = rb.getNBNumInOneInstance();
		return;
	}
	
	makeObjectFromRB2BandDontCompress(rb);
	//DEBUG(debugFile, "FreeForm converted:");
	//DEBUG(debugFile, *this);
	
	//debugFile.flush();
	
	int unCompressedSize = rb.getTheSizeOfOneNeighborhood(); //un-compressed size of the rules.
	compressedNBSize = unCompressedSize; //initializing the value, will be decremented as we delete the unimportant features.
	nbPerRule = rb.getNBNumInOneInstance();
	///bool* shouldKeep = new bool[unCompressedSize];
	///int shouldKeepIndexinOrderArray = 0;
	int featurePerNeighborhood = rb.getTheSizeOfOneNeighborhood();

	vector<int> removeOrderArray;
	utilityFunc::fillRemoveOrderArray(rb, removeOrderArray, order);
	///vector<int> copyRemoveOrderArray = removeOrderArray;
	//LOGLINE("Trying to compress the following FFRB:");
	//LOGLINE(*this);
	for (int i = 0; i < (int) removeOrderArray.size(); ++i)
	{
		if (this->compressedNBSize < 2)
			break;
		//LOG("Examining the feature ");
		//LOG(removeOrderArray[i]);
		//LOG(" of the previous FFRB tells it is ");
		if (!utilityFunc::isImportant(*this, removeOrderArray[i]))
		{
			//LOGLINE("NOT important.");
			//LOGLINE("The FFRB after removing that feature: ");
			removeFeature(removeOrderArray[i]);

			//if we are removing based on the information gain, the order should be updated after each feature elimination
			//because the information gain changes as we remove features.
			if (order == removeleastgainfirst_dynamic || order == removemostgainfirst_dynamic) 
				utilityFunc::updateRemoveOrderArrayDynamic (*this, removeOrderArray, order);
			else
				utilityFunc::updateRemoveOrderArrayStatic (removeOrderArray, i);
			

			///shouldKeep[shouldKeepIndexinOrderArray++] = false;
			clearRepeatingRules();
			featurePerNeighborhood--;
			i--;
			removedSoFar++;
			//LOGLINE(*this);
			//LOGLINE("-----------------------------------------------------------------------------------------------");
		}
		else
		{
			//LOGLINE("IMPORTANT.");
			///shouldKeep[shouldKeepIndexinOrderArray++] = true;
		}
		//logFile.flush();
	}
	
	if (this->compressedNBSize + removedSoFar != rb.getTheSizeOfOneNeighborhood())
	{
		cerr << "Shit happened!";
		assert ("The number of removed neighbours is not correct.");
	}
	//neighborIndices = new int[compressedNBSize];
	//int compIndex = 0;	//counter for the compressed neighbors.
	//for (int i = 0; i < unCompressedSize; ++i)
	//	if (shouldKeep[i])
	//		neighborIndices[compIndex++] = copyRemoveOrderArray[i];
	//this->cmpRule.clear();
	//for (int i = 0; i < rb.getInstanceNum(); ++i)
	//	cmpRule.push_back(FreeFormRule(rb.getInstance(i), neighborIndices, compressedNBSize));
	//clearRepeatingRules();
	///delete[] shouldKeep;
}

void FreeFormRuleBase::removeUnimportantsFromSpiralAddressSpace (discardingOrder order)
{
	cout << "#";
	int removedSoFar = 0;
	//ofstream debugFile ("debug.txt");
	//DEBUG(debugFile, "RuleBase2B object:");
	//DEBUG(debugFile, rb);
	if (this->neighborIndices == NULL || cmpRule.size() == 0)
	{
		cerr << "Warning: this FreeFormRuleBase object is missing either the rules or neighbor indices, it's fishy!";
		delete[] neighborIndices;
		this->cmpRule.clear();
	}
	clearRepeatingRules();
	if (cmpRule.size() == 2)
	{
		neighborIndices = new int[1];
		neighborIndices[0] = 0;
		string onlyFeature;
		int historySteps = cmpRule[0].getHistorySize();
		originalNBSize = cmpRule[0].getFeaturesPerHistory();
		for (int i = 0; i < historySteps - 1; ++i)
		{
			onlyFeature += cmpRule[0].getFeature(0, i);
			onlyFeature += ",";
		}
		onlyFeature += cmpRule[0].getFeature(0, historySteps - 1);
		FreeFormRule onlyRule(onlyFeature, cmpRule[0].getLabel());
		cmpRule.clear();
		cmpRule.push_back(onlyRule); //there is only one LI in rb, any feature can be considered as the only important feature. We assume it is the first feature.
		neighborIndices = new int[1];
		neighborIndices[0] = 0;
		compressedNBSize = 1;
		nbPerRule = historySteps;
		return;
	}
	

	//The neighbors are added in a spiral, the lower the index is the older they are.
	vector<int> removeOrderArray;
	switch (order)
	{
	case removeoldestfirst:
		for (int i = 0; i < compressedNBSize; ++i)
			removeOrderArray.push_back(i);
		break;
	case removenewestfirst:
		for (int i = 0; i < compressedNBSize; ++i)
			removeOrderArray.push_back(compressedNBSize - i - 1);
		break;
	default: 
		assert ("this order is not implemented, only removeoldest and removenewest first: " || order);
	}

	//makeObjectFromRB2BandDontCompress(rb);
	//DEBUG(debugFile, "FreeForm converted:");
	//DEBUG(debugFile, *this);
	
	//debugFile.flush();
	
	//compressedNBSize = this->originalNBSize; //initializing the value, will be decremented as we delete the unimportant features.
	//nbPerRule = cmpRule[0].getHistorySize();
	int featurePerNeighborhood = originalNBSize;

	//LOGLINE("Trying to compress the following FFRB:");
	//LOGLINE(*this);
	for (int i = 0; i < (int) removeOrderArray.size(); ++i)
	{
		if (this->compressedNBSize < 2)
			break;
		//LOG("Examining the feature ");
		//LOG(removeOrderArray[i]);
		//LOG(" of the previous FFRB tells it is ");
		if (!utilityFunc::isImportant(*this, removeOrderArray[i]))
		{
			//LOGLINE("NOT important.");
			//LOGLINE("The FFRB after removing that feature: ");
			removeFeature(removeOrderArray[i]);

			//if we are removing based on the information gain, the order should be updated after each feature elimination
			//because the information gain changes as we remove features.
			if (order == removeleastgainfirst_dynamic || order == removemostgainfirst_dynamic) 
				utilityFunc::updateRemoveOrderArrayDynamic (*this, removeOrderArray, order);
			else
				utilityFunc::updateRemoveOrderArrayStatic (removeOrderArray, i);

			clearRepeatingRules();
			featurePerNeighborhood--;
			i--;
			removedSoFar++;
			//LOGLINE(*this);
			//LOGLINE("-----------------------------------------------------------------------------------------------");
		}
		else
		{
			//LOGLINE("IMPORTANT.");
			///shouldKeep[shouldKeepIndexinOrderArray++] = true;
		}
		//logFile.flush();
	}
	
	if (this->compressedNBSize + removedSoFar != originalNBSize)
	{
		cerr << "Shit happened!";
		assert ("The number of removed neighbours is not correct.");
	}

}

void FreeFormRuleBase::makeObjectFromRB2BandDontCompress(const RuleBase2B& rb)
{
	if (this->neighborIndices != NULL || cmpRule.size() != 0)
	{
		cerr << "Warning: this FreeFormRuleBase object has a not-null pointer, it's fishy!";
		delete[] neighborIndices;
		this->cmpRule.clear();
		//assert(neighborIndices);
	}
	

	originalNBSize = compressedNBSize = rb.getTheSizeOfOneNeighborhood(); //un-compressed size of the rules.
	nbPerRule = rb.getNBNumInOneInstance();
	neighborIndices = new int[compressedNBSize];
	for (int i = 0; i < compressedNBSize; ++i)
		neighborIndices[i] = i;
	for (int i = 0; i < rb.getInstanceNum(); ++i)
	{
		FreeFormRule toBeAdded(rb.getInstance(i));
		cmpRule.push_back(toBeAdded);
	}
}

FreeFormRuleBase::~FreeFormRuleBase()
{
	delete[] neighborIndices;
	cmpRule.clear();
}

int FreeFormRuleBase::getcompressedNBSize () const
{
	return this->compressedNBSize;
}

int FreeFormRuleBase::getNBNumInOneRule () const
{
	return this->nbPerRule;
}

int FreeFormRuleBase::getRuleNum () const
{
	return this->cmpRule.size();
}

int FreeFormRuleBase::getNetInfoSizeInBits() const
{
	if (cmpRule.empty())
	{
		return 0;
	}
	return (compressedNBSize * cmpRule[0].getHistorySize() * cmpRule.size() * BITS_PER_NBR);
}

/**
 * Removes the feature with the given index from the FFRB object. Note that the given index is the physical order
 * and not the logical order of the feature. For example, removing featureIndex = 2 from the nbIndices[2,3,5,6,7]
 * removes the feature 5, because nbIndices[featureIndex] = 5.
 */
void FreeFormRuleBase::removeFeature(int featureIndex)
{
	//LOG("The FFRB object received by FreeFormRuleBase::removeFeature(featureIndex):");
	//LOG(" [featureIndex = ");
	//LOGLINE(featureIndex);
	//LOGLINE("object before removal:");
	//LOGLINE(*this);
	if (featureIndex >= this->compressedNBSize)
	{
		assert (featureIndex < this->compressedNBSize && "Out of limit address");
		cerr << "Out of limit address in FreeFormRuleBase::removeFeature()";
	}
	int* newNBIndices = new int[this->compressedNBSize - 1];
	for (int i = 0; i < featureIndex; ++i)
		newNBIndices[i] = this->neighborIndices[i];
	for (int i = featureIndex+1; i < this->compressedNBSize; ++i)
		newNBIndices[i-1] = this->neighborIndices[i];
	delete[] this->neighborIndices;
	this->neighborIndices = newNBIndices;
	for (vector<FreeFormRule>::iterator i = this->cmpRule.begin(); i != this->cmpRule.end(); ++i)
		i->removeFeature(featureIndex);
	this->compressedNBSize--;
	//LOGLINE("object after removal:");
	//LOGLINE(*this);
	//LOGLINE(endl);
}

int FreeFormRuleBase::getConflictNum() const
{
	int result = 0;
	//Removing this part because repeating rules can happen after removing a feature: 110->0, 111->0, remove feature[2]
	//if (hasRepeatingRule())
	//{
	//	cout << "Error: The RuleBase2 object has a repeating rule. There has been a problem somewhere." << endl;
	//	assert(false);
	//}
	int* ruleConflictNum = new int [cmpRule.size()]; //this array holds the number of conflicting instances for each rule. Each element of this value should usually be 0 or 1.
	for (int i = 0; i < (int)cmpRule.size(); ++i)
		ruleConflictNum[i] = -1;
	for (int i = 0; i < (int)cmpRule.size(); ++i)
		if (ruleConflictNum[i] == -1)
		{
			int j = i+1;
			int conflictIndex = -1;
			while (j < (int)cmpRule.size() && conflictIndex == -1)
			{
				if (cmpRule[i].conflicts(cmpRule[j]))
					conflictIndex = j;
				++j;
			}
			
			if (conflictIndex == -1)
				ruleConflictNum[i] = 0;
			else
			{
				ruleConflictNum[i] = 1;
				ruleConflictNum[conflictIndex] = 1;
			}
		}
	for (int i = 0; i < (int)cmpRule.size(); ++i)
		result += ruleConflictNum[i];

	//This is for logging purposes, can be removed later.
	ofstream file ("ConflictReport.txt");
	if (result > 0)
	{
		file << "Conflict detected. Conflicting rules: ";
		for (int i = 0; i < (int)cmpRule.size(); ++i)
			if (ruleConflictNum[i] > 0)
				file << i << ", ";
		file << endl;
	}

	return result;
}

bool FreeFormRuleBase::hasRepeatingRule() const
{
	for (int i = 0; i < (int)cmpRule.size(); ++i)
		for (int j = i+1; j < (int)cmpRule.size(); ++j)
			if (cmpRule[i] == cmpRule[j])
				return true;
	return false;
}

//void FreeFormRuleBase::deleteRulesWithZeroThenPart ()
//{
//	vector<FreeFormRule>::const_iterator i = cmpRule.begin();
//	while (i != cmpRule.end())
//	{
//		if (i->getLabel() == 0)
//			i = cmpRule.erase(i);
//		else
//			++i;
//	}
//	cmpRule.shrink_to_fit();
//}

void FreeFormRuleBase::deleteRulesWithZeroThenPart ()
{
	cmpRule.erase(remove_if(cmpRule.begin(), cmpRule.end(), ffrLabelIsZero), cmpRule.end());
	//vector<FreeFormRule>::const_iterator i = cmpRule.begin();
	//while (i != cmpRule.end())
	//{
	//	if (i->getLabel() == 0)
	//		i = cmpRule.erase(i);
	//	else
	//		++i;
	//}
	//cmpRule.shrink_to_fit();
}


ostream& operator<<(ostream& stream,const FreeFormRuleBase& ffrb)
{
	stream << "CompressedRuleBase object:" << endl;
	for (vector<FreeFormRule>::const_iterator i = ffrb.cmpRule.begin(); i!=ffrb.cmpRule.end(); ++i)
		stream << *i << endl;
	stream << "Number of rules: " << ffrb.cmpRule.size() << endl;
	stream << "RuleBase Size: " << ffrb.compressedNBSize << endl;
	stream << "Info Size in bits: " << ffrb.getNetInfoSizeInBits() << endl;
	stream << "Neighborhood indices: ";
	for (int i = 0; i < ffrb.compressedNBSize; ++i)
		stream << ffrb.neighborIndices[i] << ", ";

	return stream;
}

void FreeFormRuleBase::clearRepeatingRules()
{
	vector<FreeFormRule>::iterator firstIndex = this->cmpRule.begin();
	vector<FreeFormRule>::iterator lastIndex = this->cmpRule.end();
	for (; firstIndex!= lastIndex; ++firstIndex)
	{
		FreeFormRule removeValue(*firstIndex);
		lastIndex = std::remove(firstIndex+1, lastIndex, removeValue);
	}
	this->cmpRule.erase(lastIndex, cmpRule.end());
}

FreeFormRuleBase::FreeFormRuleBase(const FreeFormRuleBase& left): compressedNBSize(left.compressedNBSize), nbPerRule(left.nbPerRule), originalNBSize(left.originalNBSize)
{
	//vector<FreeFormRule> cmpRule;	//!< Compressed rules, where zero or more features are removed from the original LongInstance2B objects.
	//int* neighborIndices;			//!< The indices of the remaining [important] neighbors.
	//int compressedNBSize;			//!< Number of remaining neighbors after removing the unimportant neighbors.
	neighborIndices = new int[compressedNBSize];
	for (int i = 0; i < (int) left.cmpRule.size(); ++i)
		cmpRule.push_back(left.cmpRule[i]);
	for (int i = 0; i < compressedNBSize; ++i)
		neighborIndices[i] = left.neighborIndices[i];
}
	
FreeFormRule FreeFormRuleBase::getRule (int index) const
{
	return this->cmpRule[index];
}

int FreeFormRuleBase::countRulesWithLabel (int label) const
{
	if (label >= CELLSTATES)
	{
		assert (label < CELLSTATES && "Cannot count instances with an invalid label");
		return -1;
	}
	int result = 0;
	vector<FreeFormRule>::const_iterator it = cmpRule.begin();
	for (; it != cmpRule.end(); ++it)
		if (it->getLabel() == label)
			++result;

	return result;
}

double FreeFormRuleBase::informationContent () const
{
	if (cmpRule.size() < 2 || this->compressedNBSize < 2 || CELLSTATES < 2)
		return 0;
	if (hasRepeatingRule())
	{
		assert ("Repeating rules in FFRB!");
		return -1;
	}
	double result = 0;
	int instancesOfState[CELLSTATES];	//instancesOfState[i] tells how many instances with the label equal to i we have.
	double probability[CELLSTATES];
	for (int i = 0; i != CELLSTATES; i++)
	{
		instancesOfState[i] = 0;
		probability[i] = 0.0;
	}

	//counting number of repetition for each label
	for (int i = 0; i != (int)cmpRule.size(); i++)
		instancesOfState[cmpRule[i].getLabel()] ++;
		
	for (int i = 0; i != CELLSTATES; i++)
		if (instancesOfState[i] != 0)
		{
			probability[i] = instancesOfState[i]/(double)cmpRule.size();
			result -= (probability[i]) * (log(probability[i]) / log(2.0));
		}
	return result;
}


double FreeFormRuleBase::informationGain (int featureInHistory, int historyStep/*=0*/) const
{
	double result = 0;
	if (cmpRule.size() < 2)
		return 0;

	FreeFormRuleBase** subFFRB;
	subFFRB = new FreeFormRuleBase*[CELLSTATES];
	for (int i = 0; i != CELLSTATES; i++)
	{
		subFFRB[i] = new FreeFormRuleBase(nbPerRule);
		subFFRB[i]->compressedNBSize = this->compressedNBSize - 1;
		subFFRB[i]->neighborIndices = new int[this->compressedNBSize - 1];
		for (int featureCount = 0; featureCount < featureInHistory; ++ featureCount)
		{
			subFFRB[i]->neighborIndices[featureCount] = featureCount;
		}
		for (int featureCount = featureInHistory+1; featureCount < this->compressedNBSize; ++ featureCount)
		{
			subFFRB[i]->neighborIndices[featureCount-1] = featureCount;
		}
	}

	for (int i = 0; i != (int) this->cmpRule.size(); i++)
	{
		int thisclass = this->cmpRule[i].getFeature(featureInHistory, historyStep);
		subFFRB[thisclass]->cmpRule.push_back(this->cmpRule[i]);
	}

	for (int i = 0; i != CELLSTATES; i++)
	{
		double classProbability = (subFFRB[i]->cmpRule.size() / (double) this->cmpRule.size());
		double classInfo = subFFRB[i]->informationContent();
		result +=  classProbability * classInfo;
	}
	
	for (int i = 0; i != CELLSTATES; i++)
		delete subFFRB[i];
	delete[] subFFRB;
	return (this->informationContent() - result);
}

FreeFormRuleBase::FreeFormRuleBase(int NBPerRule):neighborIndices(NULL), nbPerRule(NBPerRule), compressedNBSize(-1)
{

}

void FreeFormRuleBase::getMostGainIndicesArray_SuperposeNBs (int* mGA, int size) const
{
	if (cmpRule.size() == 0)
	{
		if (size == 0)
			return;
		else 
		{
			cerr << "There is no rule in the FFRB to return the getMostGainArrayForOneNB()\n";
			assert("There is no rule in the FFRB to return the getMostGainArrayForOneNB()");
		}
	}
	int nbSize = this->cmpRule[0].getFeaturesPerHistory();
	if (size != nbSize)
	{
		cerr << "invalid size of most gain array passed\n";
		assert (size != nbSize && "invalid size of most gain array passed\n");
	}

	//mGA has already the correct size.
	
	if (cmpRule.size() < 2)
	{
		for (int i = 0; i != nbPerRule; i++)
			mGA[i] = i;
		return;
	}

	int i = 0, j = 0;
	double* gainArray = new double [nbPerRule*nbSize];
	for (int historytep = 0; historytep < nbPerRule; ++historytep) //the number of histories is equal to nbPerRule
		for (i = 0; i != size; i++)
		{
			gainArray[(historytep*size)+i] = this->informationGain(i, historytep);
			//sortedValues[i] = gainArray[i];
		}

	if ((nbPerRule*nbSize) % size != 0)
	{
		cerr << "Shit happened! there are few neighborhoods and a half in the rule!\n";
		assert (nbPerRule % size == 0);
	}

	//int NBnum = nbPerRule / size;
	double* gainArrayPerNB = new double[nbSize];
	double* sortedValuesPerNB = new double[nbSize];
	for (int nbIndex = 0; nbIndex < nbSize; nbIndex++)
	{
		gainArrayPerNB[nbIndex] = 0;
		sortedValuesPerNB[nbIndex] = 0;
	}

	
	for (int nbIndex = 0; nbIndex < nbSize; nbIndex++)
		for (int nbSteps = 0; nbSteps < nbPerRule; nbSteps++)
		{
			gainArrayPerNB[nbIndex] += gainArray[nbIndex + nbSteps*size];
		}

	delete[] gainArray;
	//delete[] sortedValues;

	for (int nbIndex = 0; nbIndex < nbSize; nbIndex++)
	{
		gainArrayPerNB[nbIndex] /= (double) nbPerRule;
		//cout << "Gain of feature " << nbIndex << ": " << gainArrayPerNB[nbIndex] << endl;
		sortedValuesPerNB[nbIndex] = gainArrayPerNB[nbIndex];
	}

	insertionSort<double>(sortedValuesPerNB, nbSize);

	for (i = 0; i != nbSize; i++)
	{
		while (gainArrayPerNB[j] != sortedValuesPerNB[i] && j < nbSize)
			j++;
		mGA[i] = j;
		gainArrayPerNB[j] = -2;
		j = 0;
	}

	delete[] gainArrayPerNB;
	delete[] sortedValuesPerNB;
}

FreeFormRuleBase::FreeFormRuleBase(string fileName, int layersInIfPart): originalNBSize(0), compressedNBSize(0)
{
	EightLayerIM source(fileName, layersInIfPart+1);
	int indexToAdd = 0;
	//addNeighbour(source, indexToAdd);
	cmpRule.clear();
	string ifPart;
	//the rules in the rulebase are created for each possible coordinate. We have lots of repeating rules
	//but we do not care for now. We also know that the rule i is corresponding to pixel(i%row , i/row).
	
	for (int r = 0; r < source.getRowNum(); ++r)
		for (int c = 0; c < source.getColNum(); ++c)
		{
			ifPart = "";
			for (int layer = 0; layer < layersInIfPart-1; ++layer)
			{
				ifPart += int2Str( source.getPixel(layer,r,c) );
				ifPart += ",";
			}
			ifPart += int2Str(source.getPixel(layersInIfPart-1, r, c));
			//we have the "if-part" here.
			int label = source.getPixel(layersInIfPart, r, c);
			cmpRule.push_back(FreeFormRule(ifPart, label)); //adding the first element of the rule
		}
	originalNBSize = 1;
	compressedNBSize = 1;
	neighborIndices = new int[1];
	neighborIndices[0] = 0;
	//the rules in the rulebase are created here, with lots of repeating rules.
	while (getConflictNum() > 0)
	{
		addNeighbourToRBWithRepeatingRules(source, ++indexToAdd);
		originalNBSize ++; //let's update it here and not in addNeighbour() so it does not change if we call addNeighbour() later.
	}

	nbPerRule = layersInIfPart;
	clearRepeatingRules();

}

FreeFormRuleBase::FreeFormRuleBase(string fileName, int startLayer, int labelLayer)
{
	EightLayerIM source(fileName, 8);
	int indexToAdd = 0;
	//addNeighbour(source, indexToAdd);
	cmpRule.clear();
	string ifPart;
	//the rules in the rulebase are created for each possible coordinate. We have lots of repeating rules
	//but we do not care for now. We also know that the rule i is corresponding to pixel(i%row , i/row).
	
	for (int r = 0; r < source.getRowNum(); ++r)
		for (int c = 0; c < source.getColNum(); ++c)
		{
			ifPart = "";
			for (int layer = startLayer; layer < labelLayer-1; ++layer)
			{
				ifPart += int2Str( source.getPixel(layer,r,c) );
				ifPart += ",";
			}
			ifPart += int2Str(source.getPixel(labelLayer-1, r, c));
			//we have the "if-part" here.
			int label = source.getPixel(labelLayer, r, c);
			cmpRule.push_back(FreeFormRule(ifPart, label)); //adding the first element of the rule
		}
	originalNBSize = 1;
	compressedNBSize = 1;
	neighborIndices = new int[1];
	neighborIndices[0] = 0;
	//the rules in the rulebase are created here, with lots of repeating rules.
	while (getConflictNum() > 0)
	{
		addNeighbourToRBWithRepeatingRules(source, ++indexToAdd, startLayer, labelLayer);
		originalNBSize ++; //let's update it here and not in addNeighbour() so it does not change if we call addNeighbour() later.
	}

	nbPerRule = labelLayer - startLayer;
	clearRepeatingRules();

}

bool FreeFormRuleBase::addNeighbourToRBWithRepeatingRules(const EightLayerIM& source, int nbIndex)
{
	for (int i = 0; i < this->compressedNBSize; ++i)
		if (nbIndex == this->neighborIndices[i])
			return false;

	//creating the new nbIndices array
	++compressedNBSize;
	int* newNBIndices = new int[compressedNBSize];
	for (int i = 0; i <compressedNBSize - 1; ++i)
		newNBIndices[i] = neighborIndices[i];
	newNBIndices[compressedNBSize - 1] = nbIndex;

	delete[] neighborIndices;
	neighborIndices = newNBIndices;

	int* featuresToBeAdded = new int[source.getValidImages() -1]; //one is for the label
	for (int i = 0; i < (int) cmpRule.size(); ++i)
	{
		int row = i/MCOLS;
		int col = i%MCOLS;
		
		for (int layer = 0; layer < source.getValidImages()-1; ++layer)
			featuresToBeAdded[layer] = source.getPixelValueWithRotationalIndex(layer,row,col,nbIndex);
		cmpRule[i].addFeature(featuresToBeAdded, source.getValidImages() -1);
	}
	delete[] featuresToBeAdded;
	return true;
}

bool FreeFormRuleBase::addNeighbourToRBWithRepeatingRules(const EightLayerIM& source, int nbIndex, int startBit, int labelLayer)
{
	assert(source.getValidImages() >= labelLayer && "Not enough valid layers in EightLayerIM object");
	for (int i = 0; i < this->compressedNBSize; ++i)
		if (nbIndex == this->neighborIndices[i])
			return false;

	//creating the new nbIndices array
	++compressedNBSize;
	int* newNBIndices = new int[compressedNBSize];
	for (int i = 0; i <compressedNBSize - 1; ++i)
		newNBIndices[i] = neighborIndices[i];
	newNBIndices[compressedNBSize - 1] = nbIndex;

	delete[] neighborIndices;
	neighborIndices = newNBIndices;

	int* featuresToBeAdded = new int[labelLayer - startBit]; //one is for the label
	for (int i = 0; i < (int) cmpRule.size(); ++i)
	{
		int row = i/MCOLS;
		int col = i%MCOLS;
		
		for (int layer = startBit; layer < labelLayer; ++layer)
			featuresToBeAdded[layer-startBit] = source.getPixelValueWithRotationalIndex(layer,row,col,nbIndex);
		cmpRule[i].addFeature(featuresToBeAdded, labelLayer-startBit);
	}
	delete[] featuresToBeAdded;
	return true;
}

vector<int> FreeFormRuleBase::getNeighborIndices () const
{
	vector<int> result (compressedNBSize);
	for (int i = 0; i < compressedNBSize; ++i)
		result.push_back(neighborIndices[i]);
	return result;
}